{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "\n",
    "## <center> [mlcourse.ai](https://mlcourse.ai) – открытый курс OpenDataScience по машинному обучению \n",
    "\n",
    "    \n",
    "Автор материала: Ольга Дайховская (@aiho в Slack ODS). Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center> Домашнее задание № 4 (демо).\n",
    "## <center>  Прогнозирование популярности статей на TechMedia (Хабр) с помощью линейных моделей\n",
    "    \n",
    "**В задании Вам предлагается разобраться с тем, как работает TfidfVectorizer и DictVectorizer, затем обучить и настроить модель линейной регрессии Ridge на данных о публикациях на Хабрахабре. Пройдя все шаги, вы сможете получить бейзлайн для [соревнования](https://www.kaggle.com/c/howpop-habrahabr-favs-lognorm) (несмотря на old в названии, для этого задания соревнование актуально). \n",
    "Ответьте на все вопросы в этой тетрадке и заполните ответы в [гугл-форме](https://docs.google.com/forms/d/1gPt401drm84N2kdezwGWtPJN_JpaFqXoh6IwlWOslb4).**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Описание соревнования**\n",
    "\n",
    "Предскажите, как много звездочек наберет статья, зная только ее текст и время публикации\n",
    "\n",
    "Необходимо предсказать популярность поста на Хабре по содержанию и времени публикации. Как известно, пользователи Хабра могут добавлять статьи к себе в избранное. Общее количество пользователей, которое это сделали отображается у статьи количеством звездочек. Будем считать, что число звездочек, поставленных статье, наиболее хорошо отражает ее популярность.\n",
    "\n",
    "Более формально, в качестве метрики популярности статьи будем использовать долю статей за последний месяц, у которых количество звездочек меньше чем у текущей статьи. А точнее, доле числа звездочек можно поставить в соответствие квантили стандартного распределения, таким образом получаем числовую характеристику популярности статьи. Популярность статьи 0 означает, что статья получила ровно столько звездочек, сколько в среднем получают статьи. И соответственно чем больше звездочек получила статья по сравнению со средним, тем выше это число."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Приступим:** импортируем необходимые библиотеки и скачаем данные"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import scipy\n",
    "from sklearn.feature_extraction import DictVectorizer\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer\n",
    "from sklearn.linear_model import Ridge\n",
    "from sklearn.metrics import mean_squared_error\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "%matplotlib inline\n",
    "from matplotlib import pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Скачайте [данные](https://drive.google.com/file/d/1nV2qV9otN3LnVSDqy95hvpJdb6aWtATk/view?usp=sharing) соревнования (данные были удалены с Kaggle ради организации последующего идентичного соревнования, так что тут ссылка на Google Drive)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# при необходимости поменяйте путь к данным \n",
    "train_df = pd.read_csv('../../data/howpop_train.csv')\n",
    "test_df  = pd.read_csv('../../data/howpop_test.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df.head(1).T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df.shape, test_df.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Убедимся, что данные отсортированы по признаку `published`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df['published'].apply(lambda ts: pd.to_datetime(ts).value).plot();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Чтобы ответить на вопросы 1 и 2, можно использовать [pandas.DataFrame.corr()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html), [pandas.to_datetime()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.to_datetime.html) и [pandas.Series.value_counts()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.value_counts.html)**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color='red'>Вопрос 1.</font> Есть ли в train_df признаки, корреляция между которыми больше 0.9? Обратите внимание, именно различные признаки - корреляция признака с самим собой естественно больше 0.9 :)\n",
    "- да\n",
    "- нет\n",
    "- не знаю"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color='red'>Вопрос 2.</font> В каком году было больше всего публикаций? (Рассматриваем train_df)\n",
    "- 2014\n",
    "- 2015\n",
    "- 2016\n",
    "- 2017"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Разбиение на train/valid\n",
    "Используем только признаки 'author', 'flow', 'domain' и 'title'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "features = ['author', 'flow', 'domain','title']\n",
    "train_size = int(0.7 * train_df.shape[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(train_df), train_size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X, y = train_df.loc[:, features],  train_df['favs_lognorm'] #отделяем признаки от целевой переменной\n",
    "\n",
    "X_test = test_df.loc[:, features]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_valid = X.iloc[:train_size, :], X.iloc[train_size:,:]\n",
    "\n",
    "y_train, y_valid = y.iloc[:train_size], y.iloc[train_size:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Применение TfidfVectorizer\n",
    "\n",
    "**TF-IDF** (от англ. TF — term frequency, IDF — inverse document frequency) — статистическая мера, используемая для оценки важности слова в контексте документа, являющегося частью коллекции документов или корпуса. Вес некоторого слова пропорционален количеству употребления этого слова в документе, и обратно пропорционален частоте употребления слова в других документах коллекции. [Подробнее в источнике](https://ru.wikipedia.org/wiki/TF-IDF)\n",
    "\n",
    "TfidfVectorizer преобразует тексты в матрицу TF-IDF признаков.\n",
    "\n",
    "**Основные параметры TfidfVectorizer в sklearn:**\n",
    "- **min_df** - при построении словаря слова, которые встречаются *реже*, чем указанное значение, игнорируются\n",
    "- **max_df** - при построении словаря слова, которые встречаются *чаще*, чем указанное значение, игнорируются\n",
    "- **analyzer** - определяет, строятся ли признаки по словам или по символам (буквам)\n",
    "- **ngram_range** - определяет, формируются ли признаки только из отдельных слов или из нескольких слов (в случае с analyzer='char' задает количество символов). Например, если указать analyzer='word' и ngram_range=(1,3),то признаки будут формироваться из отдельных слов, из пар слов и из троек слов.\n",
    "- **stop_words** - слова, которые игнорируются при построении матрицы\n",
    "\n",
    "Более подробно с параметрами можно ознакомиться в [документации](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Инициализируйте TfidfVectorizer с параметрами min_df=3, max_df=0.3 и ngram_range=(1, 3).<br />\n",
    "Примените метод fit_transform к X_train['title'] и метод transform к X_valid['title'] и X_test['title']**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color='red'>Вопрос 3.</font> Какой размер у полученного словаря?\n",
    "- 43789\n",
    "- 50624\n",
    "- 93895\n",
    "- 74378"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vectorizer_title = # ваш код здесь\n",
    "\n",
    "X_train_title = # и здесь\n",
    "X_valid_title = # и тут тоже\n",
    "X_test_title = # и тут"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Можно посмотреть словарь в виде {'термин': индекс признака,...}\n",
    "vectorizer_title.vocabulary_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color='red'>Вопрос 4.</font> Какой индекс у слова 'python'?\n",
    "- 1\n",
    "- 10\n",
    "- 9065\n",
    "- 15679"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Инициализируйте TfidfVectorizer, указав analyzer='char'.<br />\n",
    "Примените метод fit_transform к X_train['title'] и метод transform к X_valid['title'] и X_test['title']**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color='red'>Вопрос 5.</font> Какой размер у полученного словаря?\n",
    "- 218\n",
    "- 510\n",
    "- 125\n",
    "- 981"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vectorizer_title_ch = # ваш код здесь\n",
    "\n",
    "X_train_title_ch = #...\n",
    "X_valid_title_ch = #...\n",
    "X_test_title_ch = #..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Здесь так же можно посмотреть словарь\n",
    "# Заметьте насколько отличаются словари для TfidfVectorizer с analyzer='word' и analyzer='char'\n",
    "vectorizer_title_ch.vocabulary_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Работа с категориальными признаками\n",
    "\n",
    "Для обработки категориальных признаков 'author', 'flow', 'domain' мы будем использовать DictVectorizer из sklearn."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "feats = ['author', 'flow', 'domain']\n",
    "X_train[feats][:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим как он работает на примере первых пяти строк"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# сначала заполняем пропуски прочерком\n",
    "X_train[feats][:5].fillna('-')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Преобразуем датафрейм в словарь, где ключами являются индексы объектов (именно для этого мы транспонировали датафрейм),\n",
    "# а значениями являются словари в виде 'название_колонки':'значение'\n",
    "X_train[feats][:5].fillna('-').T.to_dict()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# В DictVectorizer нам нужно будет передать список словарей для каждого объекта в виде 'название_колонки':'значение',\n",
    "# поэтому используем .values()\n",
    "X_train[feats][:5].fillna('-').T.to_dict().values()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# В итоге получается разреженная матрица\n",
    "dict_vect = DictVectorizer()\n",
    "dict_vect_matrix = dict_vect.fit_transform(X_train[feats][:5].fillna('-').T.to_dict().values())\n",
    "dict_vect_matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Но можно преобразовать ее в numpy array с помощью .toarray()\n",
    "dict_vect_matrix.toarray()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# В получившейся матрице 5 строк (по числу объектов) и 9 столбцов\n",
    "# Далее разберемся почему колонок именно 9\n",
    "dict_vect_matrix.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим сколько уникальных значений в каждой колонке.<br />\n",
    "Суммарно их 9 - столько же, сколько и колонок. Это объясняется тем, что для категориальных признаков со строковыми значениями DictVectorizer делает кодирование бинарными признаками - каждому уникальному значению признака соответствует один новый бинарный признак, который равен 1 только в том случае, если в исходной матрице этот признак принимает значение, которому соответствует эта колонка новой матрицы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in feats:\n",
    "    print(col,len(X_train[col][:5].fillna('-').unique()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Также можно посмотреть что означает каждая колонка полученной матрицы"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# например, самая первая колонка называется 'author=@DezmASter' - то есть принимает значение 1 только если автор @DezmASter\n",
    "dict_vect.feature_names_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Инициализируйте DictVectorizer с параметрами по умолчанию.<br />\n",
    "Примените метод fit_transform к X_train[feats] и метод transform к X_valid[feats] и X_test[feats]**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vectorizer_feats = #ваш код здесь\n",
    "\n",
    "X_train_feats = #...\n",
    "X_valid_feats = #...\n",
    "X_test_feats = #..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_feats.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Соединим все полученные матрицы при помощи scipy.sparse.hstack()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_new = scipy.sparse.hstack([X_train_title, X_train_feats, X_train_title_ch])\n",
    "X_valid_new = scipy.sparse.hstack([X_valid_title, X_valid_feats, X_valid_title_ch])\n",
    "X_test_new =  scipy.sparse.hstack([X_test_title, X_test_feats, X_test_title_ch])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Обучение модели\n",
    "\n",
    "Далее будем использовать Ridge, линейную модель с l2-регуляризацией.\n",
    "[Документация](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html)\n",
    "\n",
    "Основной параметр Ridge - **alpha, коэффициент регуляризации**. Регуляризация используется для улучшения обобщающей способности модели - прибавляя к функционалу потерь сумму квадратов весов, умноженную на коэффициент регуляризации (та самая alpha), мы штрафуем модель за слишком большие значения весов и не позволяем ей переобучаться. Чем больше этот коээфициент, тем сильнее эффект."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Обучите две модели на X_train_new, y_train, задав в первой alpha=0.1 и random_state = 1, а во второй alpha=1.0 и random_state = 1**\n",
    "\n",
    "**Рассчитайте среднеквадратичную ошибку каждой модели (mean_squared_error). Сравните значения ошибки на обучающей и тестовой выборках и ответьте на вопросы.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color='red'>Вопрос 6.</font> Выберите верные утверждения:\n",
    "- обе модели показывают одинаковый результат (среднеквадратичная ошибка отличается не больше чем на тысячные), регуляризация ничего не меняет\n",
    "- при alpha=0.1 модель переобучается\n",
    "- среднеквадратичная ошибка первой модели на тесте меньше\n",
    "- при alpha=1.0 у модели обобщающая способность лучше, чем у при alpha=0.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "model1 = #ваш код здесь\n",
    "#здесь тоже ваш код"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_preds1 = model1.predict(X_train_new)\n",
    "valid_preds1 = model1.predict(X_valid_new)\n",
    "\n",
    "print('Ошибка на трейне',mean_squared_error(y_train, train_preds1))\n",
    "print('Ошибка на тесте',mean_squared_error(y_valid, valid_preds1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "model2 = #ваш код здесь\n",
    "#здесь тоже ваш код"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_preds2 = model2.predict(X_train_new)\n",
    "valid_preds2 = model2.predict(X_valid_new)\n",
    "\n",
    "print('Ошибка на трейне',mean_squared_error(y_train, train_preds2))\n",
    "print('Ошибка на тесте',mean_squared_error(y_valid, valid_preds2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Baseline\n",
    "\n",
    "**Теперь попытаемся получить бейзлайн для соревования - используйте Ridge с параметрами по умолчанию и обучите модель на всех данных - соедините X_train_new X_valid_new (используйте scipy.sparse.vstack()), а целевой переменной будет y.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "model = # ваш код здесь\n",
    "\n",
    "# обучите модель на всех данных\n",
    "\n",
    "test_preds = model.predict(X_test_new)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_submission = pd.read_csv('../../data/habr_sample_submission.csv', \n",
    "                                index_col='url')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_submission.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ridge_submission = sample_submission.copy()\n",
    "ridge_submission['favs_lognorm'] = test_preds\n",
    "# это будет бейзлайн \"Простое решение\"\n",
    "ridge_submission.to_csv('ridge_baseline.csv') "
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
